package com.chensi.handler;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.StringUtils;

import com.chensi.annotation.CacheEvict;
import com.chensi.annotation.CacheMethod;
import com.chensi.annotation.Cacheable;
import com.chensi.common.Global;
import com.chensi.util.JedisUtils;

/**
 * Aop拦截缓存逻辑
 * @author Chason
 * @version 2016-1-14 下午4:13:10
 */
@Aspect
public class AopCache {
	private static final Logger log = LoggerFactory.getLogger(AopCache.class);

	/**
	 * 定义缓存逻辑
	 */
	@Around("@annotation(com.chensi.annotation.Cacheable)")
	public Object cache(ProceedingJoinPoint pjp) {
		Object result = null;
		boolean cacheEnable = Global.isCacheMode();
		// 判断是否开启缓存
		if (!cacheEnable) {
			try {
				result = pjp.proceed();
			} catch (Throwable e) {
				log.error(e.toString());
				e.printStackTrace();
			}
			return result;
		}
		log.info("进入缓存");
		Method method = getMethod(pjp);
		Cacheable cacheable = method.getAnnotation(Cacheable.class);
		String key = cacheable.value();
		if (cacheable.method().length > 0 && cacheable.method()[0].equals(CacheMethod.PK)) {
			// 主键合并
			key = key + "_" + pjp.getArgs()[0];
		}

		// 获取方法的返回类型,让缓存可以返回正确的类型
		Class returnType = ((MethodSignature) pjp.getSignature()).getReturnType();
		// JedisUtils==>按照指定的redis方法查询，object支持string
		if (String.class.isAssignableFrom(returnType)) {
			result = JedisUtils.get(key);
		} else if (List.class.isAssignableFrom(returnType)) {
			result = JedisUtils.getObjectList(key);
		} else if (Set.class.isAssignableFrom(returnType)) {
			result = JedisUtils.getObjectSet(key);
		} else if (Map.class.isAssignableFrom(returnType)) {
			result = JedisUtils.getObjectMap(key);
		} else {
			result = JedisUtils.getObject(key);
		}
		if (result == null) {
			try {
				result = pjp.proceed();
				if (result != null) {
					log.debug("缓存中不存在，将执行结果写入缓存");
					if (String.class.isAssignableFrom(returnType)) {
						JedisUtils.set(key, (String) result, cacheable.expireTime());
					} else if (List.class.isAssignableFrom(returnType)) {
						JedisUtils.setObjectList(key, (List) result, cacheable.expireTime());
					} else if (Set.class.isAssignableFrom(returnType)) {
						JedisUtils.setObjectSet(key, (Set) result, cacheable.expireTime());
					} else if (Map.class.isAssignableFrom(returnType)) {
						JedisUtils.setObjectMap(key, (Map) result, cacheable.expireTime());
					} else {
						JedisUtils.setObject(key, result, cacheable.expireTime());
					}
					return result;
				} else {
					log.debug("缓存和dao都为空,直接返回null");
					return result;
				}
			} catch (Throwable e) {
				log.error(e.toString());
			}
		}
		log.debug("缓存中存在，直接获取");
		return result;
	}

	/** 定义清除缓存逻辑 */
	@Around(value = "@annotation(com.chensi.annotation.CacheEvict)")
	public Object evict(ProceedingJoinPoint pjp) {
		Object result = null;
		boolean cacheEnable = Global.isCacheMode();
		// 判断是否开启缓存
		if (!cacheEnable) {
			try {
				result = pjp.proceed();
			} catch (Throwable e) {
				log.error(e.toString());
			}
			return result;
		}
		log.info("进入缓存");
		Method method = getMethod(pjp);
		CacheEvict cacheEvict = method.getAnnotation(CacheEvict.class);
		String value = cacheEvict.value();
		String key = parseKey(cacheEvict.key(), method, pjp.getArgs());
		if (StringUtils.hasLength(key)) {
			value = value + "_" + key;
		}
		try {
			result = pjp.proceed();
			JedisUtils.delObject(value);
		} catch (Throwable e) {
			log.error(e.toString());
		}
		log.info("缓存 {} 已清除", value);
		return result;
	}

	/**
	 * 获取被拦截方法对象
	 * 
	 * MethodSignature.getMethod() 获取的是顶层接口或者父类的方法对象 而缓存的注解在实现类的方法上
	 * 所以应该使用反射获取当前对象的方法对象
	 * 
	 * 基本数据类型反射会变为包装类型，所以反射前的类型尽量不要写基本数据类型,会报NoSuchMethodException异常
	 */
	private Method getMethod(ProceedingJoinPoint pjp) {
		// 获取参数的类型
		Object[] args = pjp.getArgs();
		Class[] argTypes = new Class[pjp.getArgs().length];
		for (int i = 0; i < args.length; i++) {
			argTypes[i] = args[i].getClass();
		}
		Method method = null;
		try {
			method = pjp.getTarget().getClass().getMethod(pjp.getSignature().getName(), argTypes);
		} catch (NoSuchMethodException e) {
			log.error("获取被拦截方法对象出错", e);
		} catch (SecurityException e) {
			log.error("获取被拦截方法对象出错", e);
		}
		return method;

	}

	/**
	 * 获取缓存的key key 定义在注解上，支持SPEL表达式:#u.id==>getId()
	 * @param pjp
	 * @return
	 */
	private String parseKey(String key, Method method, Object[] args) {
		// 获取被拦截方法参数名列表(使用Spring支持类库)
		LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
		String[] paraNameArr = u.getParameterNames(method);

		// 使用SPEL进行key的解析
		ExpressionParser parser = new SpelExpressionParser();
		// SPEL上下文
		StandardEvaluationContext context = new StandardEvaluationContext();
		// 把方法参数放入SPEL上下文中
		for (int i = 0; i < paraNameArr.length; i++) {
			context.setVariable(paraNameArr[i], args[i]);
		}
		return StringUtils.hasLength(key) ? parser.parseExpression(key).getValue(context, String.class) : null;
	}

}
